#ifndef DISNEY_BRDF
#define DISNEY_BRDF
#include"BxDF.h"
class DisneyBRDF :public BxDF
{
#define DEFAULT_METALLIC 0
#define DEFAULT_SUBSURFACE 0
#define DEFAULT_SPECULAR .2f
#define DEFAULT_ROUGHNESS .7f
#define DEFAULT_SPECULARTINT 0
#define DEFAULT_ANISOTROPIC 0
#define DEFAULT_SHEEN 0
#define DEFAULT_SHEENTINT .5f
#define DEFAULT_CLEARCOAT 0
#define DEFAULT_CLEARCOATGLOSS 1
private:
	Spectrum basecolor;
	Float metallic;
	Float subsurface;
	Float specular;
	Float roughness;
	Float specularTint;
	Float anisotropic;
	Float sheen;
	Float sheenTint;
	Float clearcoat;
	Float clearcoatGloss;
	Float prob[3];

	Float SchlickFresnel(Float u)
	{
		Float m = clamp(1 - u, 0, 1);
		Float m2 = m * m;
		return m2 * m2*m; // pow(m,5)
	}

	Float GTR1(Float NdotH, Float a)
	{
		if (a >= 1) return 1 / Pi;
		Float a2 = a * a;
		Float t = 1 + (a2 - 1)*NdotH*NdotH;
		return (a2 - 1) / (Pi*log(a2)*t);
	}

	Float GTR2(Float NdotH, Float a)
	{
		Float a2 = a * a;
		Float t = 1 + (a2 - 1)*NdotH*NdotH;
		return a2 / (Pi * t*t);
	}

	Float GTR2_aniso(Float NdotH, Float HdotX, Float HdotY, Float ax, Float ay)
	{
		return 1 / (Pi * ax*ay * sqr(sqr(HdotX / ax) + sqr(HdotY / ay) + NdotH * NdotH));
	}

	Float smithG_GGX(Float NdotV, Float alphaG)
	{
		Float a = alphaG * alphaG;
		Float b = NdotV * NdotV;
		return 1 / (NdotV + sqrtf(a + b - a * b));
	}

	Float smithG_GGX_aniso(Float NdotV, Float VdotX, Float VdotY, Float ax, Float ay)
	{
		return 1 / (NdotV + sqrtf(sqr(VdotX*ax) + sqr(VdotY*ay) + sqr(NdotV)));
	}

	P3 mon2lin(Spectrum &t)
	{
		Float rgb[3];
		t.ToRGB(rgb);
		return P3(powf(rgb[0], 2.2f), powf(rgb[1], 2.2f), powf(rgb[2], 2.2f));
	}
	P3 hightlight_sample(P3 &light, Float* rands, Float *pdf)
	{
		Float funcPhiH;

		Float aspect = sqrtf(1 - anisotropic * .9f);
		Float ax = max(.001f, sqr(roughness) / aspect);
		Float ay = max(.001f, sqr(roughness)*aspect);
		Float t = 2 * Pi*rands[0], sint = sin(t), cost = cos(t);
		Float r2 = ay * ay*sint*sint + ax * ax*cost*cost, r = sqrtf(r2);
		funcPhiH = 1.f / r2;
		Float sinPhiH = sint * ay / r, cosPhiH = cost * ax / r;

		auto t1 = funcPhiH * (1 - rands[1]);
		auto t2 = (1 - funcPhiH)*rands[1] + funcPhiH;

		Float cos2ThetaH = t1 / t2, cosThetaH = sqrtf(cos2ThetaH);
		Float sin2ThetaH = 1 - cos2ThetaH, sinThetaH = sqrtf(sin2ThetaH);

		Float D = 1.f / (Pi*ax*ay*sqr((sin2ThetaH*funcPhiH) + cos2ThetaH));

		P3 wh(sinThetaH*cosPhiH,sinThetaH*sinPhiH,cosThetaH);
		Float len = 2 * light.dot(wh);
		P3 dir = wh * len - light;
		*pdf = D * cosThetaH / (4 * wh.dot(dir));
		return dir;
	}
	P3 clearcoat_sample(P3 &light, Float* rands, Float *pdf)
	{
		
		Float phiH = 2 * Pi*rands[0];
		Float a = max(.001f, sqr(roughness)), a2 = a * a;
		Float cos2ThetaH = (pow(a, 2 - 2 * rands[1]) - 1) / (a2 - 1),cosThetaH = sqrtf(cos2ThetaH);
		Float sin2ThetaH = (1 - cos2ThetaH), sinThetaH = sqrtf(sin2ThetaH);
		P3 wh(sinThetaH*cos(phiH), sinThetaH*sin(phiH),cosThetaH);
		Float len = 2 * light.dot(wh);
		P3 dir =  wh * len - light;
		Float D = (a2 - 1) / (2 * Pi*log(a)*(a2*cos2ThetaH+sin2ThetaH));
		*pdf = D * cosThetaH / (4 * wh.dot(dir));
		return dir;
		
		/*
		Float alpha2 = clearcoatGloss * clearcoatGloss;
		Float cosTheta = sqrtf(max(0, (1 - pow(alpha2, 1 - rands[0])) / (1 - alpha2)));
		Float sinTheta = sqrtf(max(0, 1 - cosTheta * cosTheta));
		Float phi = 2 * Pi*rands[1];
		P3 w = n, u = ((fabs(w.x) > 0.1f ? P3(0, 1, 0) : P3(1, 0, 0)).cross(w)).norm(), v = w.cross(u);
		P3 wh = u * sinTheta*cos(phi) + v * sinTheta*sin(phi) + w * cosTheta;
		P3 dir = -light.reflect(wh);
		Float NH = abs(n.dot(wh));
		Float Dr = GTR1(NH, clearcoatGloss);
		*pdf =  Dr * NH / (4 * light.dot(wh));
		return dir;
		*/
	}
public:
	DisneyBRDF(Spectrum _basecolor,Float _metallic= DEFAULT_METALLIC, Float _subsurface= DEFAULT_SUBSURFACE, Float _spec= DEFAULT_SPECULAR,
		Float _roughness= DEFAULT_ROUGHNESS,Float _specTint= DEFAULT_SPECULARTINT, Float _aniso=DEFAULT_ANISOTROPIC,
		Float _sheen= DEFAULT_SHEEN, Float _sheenTint= DEFAULT_SHEENTINT, Float _cc= DEFAULT_CLEARCOAT, Float _ccGloss= DEFAULT_CLEARCOATGLOSS) :
		basecolor(_basecolor),metallic(_metallic), subsurface(_subsurface),specular(_spec),roughness(_roughness),specularTint(_specTint),anisotropic(_aniso),
		sheen(_sheen),sheenTint(_sheenTint),clearcoat(_cc),clearcoatGloss(_ccGloss), BxDF(BxDFType(BSDF_GLOSSY|BSDF_REFLECTION))
	{
		prob[0] = clamp(1-metallic,0.001f,0.8f); //prob of sampling diffuse
		prob[1] = (1-prob[0])*(1.f/(1+clearcoat)); //highlight
		prob[2] = 1 - prob[0] - prob[1]; //clearcoat
	}
	Spectrum f(P3 &light, P3 &view)
	{
		
		Float NdotL = light.z;
		Float NdotV = view.z;
		if (NdotL < 0 || NdotV < 0)
		{
			return Spectrum();
		}

		P3 H = (light+view).norm();
		Float NdotH = H.z;
		Float LdotH = light.dot(H);

		P3 Cdlin = mon2lin(basecolor);
		Float Cdlum = .3f*Cdlin.x + .6f*Cdlin.y + .1f*Cdlin.z; // luminance approx.

		P3 Ctint = Cdlum > 0 ? Cdlin / Cdlum : P3(1); // normalize lum. to isolate hue+sat
		P3 Cspec0 = mix(mix(P3(1), Ctint, specularTint)*specular*.08f, Cdlin, metallic);
		P3 Csheen = mix(P3(1), Ctint, sheenTint);

		// Diffuse fresnel - go from 1 at normal incidence to .5 at grazing
		// and mix in diffuse retro-reflection based on roughness
		Float FL = SchlickFresnel(NdotL), FV = SchlickFresnel(NdotV);
		Float Fd90 = 0.5f + 2 * LdotH*LdotH * roughness;
		Float Fd = mix(1.0f, Fd90, FL) * mix(1.0f, Fd90, FV);

		// Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf
		// 1.25 scale is used to (roughly) preserve albedo
		// Fss90 used to "flatten" retroreflection based on roughness
		Float Fss90 = LdotH * LdotH*roughness;
		Float Fss = mix(1.0f, Fss90, FL) * mix(1.0f, Fss90, FV);
		Float ss = 1.25f * (Fss * (1 / (NdotL + NdotV) - .5f) + .5f);

		// specular
		Float a = max(.001f, sqr(roughness));

		Float Ds = GTR2(NdotH, a);
		Float FH = SchlickFresnel(LdotH);
		P3 Fs = mix(Cspec0, P3(1), FH);
		Float Gs = smithG_GGX(NdotV, a)*smithG_GGX(NdotL,a);

		// sheen
		P3 Fsheen =Csheen * sheen *FH;

		// clearcoat (ior = 1.5 -> F0 = 0.04)
		Float Dr = GTR1(NdotH, mix(.1f, .001f, clearcoatGloss));
		Float Fr = mix(.04f, 1.0f, FH);
		Float Gr = smithG_GGX(NdotL, .25f) * smithG_GGX(NdotV, .25f);

		auto tmp = (Cdlin*(1.f / Pi)*mix(Fd, ss, subsurface) + Fsheen)
			* (1 - metallic)
			+ Fs * Ds*Gs + .25f*clearcoat*Gr*Fr*Dr;
		Float rgb[] = { tmp.x,tmp.y,tmp.z };
		return Spectrum::FromRGB(rgb, SpectrumType::Reflectance);
		
		
	}
	Spectrum f_aniso(P3 &light, P3 &view, P3 &X, P3 &Y)
	{
		Float NdotL = light.z;
		Float NdotV = view.z;
		if (NdotL < 0 || NdotV < 0) return Spectrum();

		P3 H = (light + view).norm();
		Float NdotH = H.z;
		Float LdotH = light.dot(H);

		P3 Cdlin = mon2lin(basecolor);
		Float Cdlum = .3f*Cdlin.x + .6f*Cdlin.y + .1f*Cdlin.z; // luminance approx.

		P3 Ctint = Cdlum > 0 ? Cdlin / Cdlum : P3(1); // normalize lum. to isolate hue+sat
		P3 Cspec0 = mix(mix(P3(1), Ctint, specularTint)*specular*.08f, Cdlin, metallic);
		P3 Csheen = mix(P3(1), Ctint, sheenTint);

		// Diffuse fresnel - go from 1 at normal incidence to .5 at grazing
		// and mix in diffuse retro-reflection based on roughness
		Float FL = SchlickFresnel(NdotL), FV = SchlickFresnel(NdotV);
		Float Fd90 = 0.5f + 2 * LdotH*LdotH * roughness;
		Float Fd = mix(1.0f, Fd90, FL) * mix(1.0f, Fd90, FV);

		// Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf
		// 1.25 scale is used to (roughly) preserve albedo
		// Fss90 used to "flatten" retroreflection based on roughness
		Float Fss90 = LdotH * LdotH*roughness;
		Float Fss = mix(1.0f, Fss90, FL) * mix(1.0f, Fss90, FV);
		Float ss = 1.25f * (Fss * (1 / (NdotL + NdotV) - .5f) + .5f);

		// specular
		Float aspect = sqrtf(1 - anisotropic * .9f);
	    Float ax = max(.001f, sqr(roughness) / aspect);
		Float ay = max(.001f, sqr(roughness)*aspect);
		Float Ds = GTR2_aniso(NdotH, H.dot(X), H.dot(Y), ax, ay);
		Float FH = SchlickFresnel(LdotH);
		P3 Fs = mix(Cspec0, P3(1), FH);
		Float Gs = smithG_GGX_aniso(NdotL, light.dot(X),light.dot(Y), ax, ay);
		Gs *= smithG_GGX_aniso(NdotV,view.dot(X), view.dot(Y), ax, ay);

		// sheen
		P3 Fsheen = Csheen * sheen *FH;

		// clearcoat (ior = 1.5 -> F0 = 0.04)
		Float Dr = GTR1(NdotH, mix(.1, .001f, clearcoatGloss));
		Float Fr = mix(.04f, 1.0f, FH);
		Float Gr = smithG_GGX(NdotL, .25f) * smithG_GGX(NdotV, .25f);

		auto tmp = (Cdlin*(1.f / Pi)*mix(Fd, ss, subsurface) + Fsheen)
			* (1 - metallic)
			+ Fs * Ds*Gs + .25f*clearcoat*Gr*Fr*Dr;
		Float rgb[] = { tmp.x,tmp.y,tmp.z };
		return Spectrum::FromRGB(rgb, SpectrumType::Reflectance);
	}

	virtual P3 ImportanceSample(P3 &light, Float* rands,Float *pdf)
	{
		Float p = (Float)rand() / RAND_MAX;
		if (p < prob[0]) //sample diffuse
		{
			*pdf = prob[0]*.5f*InvPi;
			return CosineSampleHemisphere(light,rands);
		}
		else if (p < prob[0]+prob[1]) //sample highlight
		{
			Float pdf1;
			P3 dir= hightlight_sample(light,rands,&pdf1);
			*pdf = prob[1] * pdf1;
			return dir;
		}
		else //sample clearcoat
		{
			Float pdf1;
			P3 dir= clearcoat_sample(light, rands, &pdf1);
			*pdf = prob[2] * pdf1;
			return dir;
		}
	}
};
#endif // !DISNEY_BRDF

